package com.xbongbong.pro.statistic.service.result;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xbongbong.i18n.util.I18nMessageUtil;
import com.xbongbong.paas.constant.PaasConstant;
import com.xbongbong.paas.domain.entity.PaasAppEntity;
import com.xbongbong.paas.domain.entity.PaasFormDataEntity;
import com.xbongbong.paas.domain.entity.PaasFormExplainEntity;
import com.xbongbong.paas.domain.entity.ext.ConditionsEntityExt;
import com.xbongbong.paas.domain.entity.ext.PaasFormDataEntityExt;
import com.xbongbong.paas.elasticsearch.pojo.XbbAggregatedPage;
import com.xbongbong.paas.elasticsearch.util.EsUtil;
import com.xbongbong.paas.enums.ConditionEnum;
import com.xbongbong.paas.enums.DelEnum;
import com.xbongbong.paas.enums.FieldTypeEnum;
import com.xbongbong.paas.enums.IndexTypeEnum;
import com.xbongbong.paas.field.FieldAttrEntity;
import com.xbongbong.paas.help.subform.TransferSubFormHelper;
import com.xbongbong.paas.model.PaasAppModel;
import com.xbongbong.paas.model.PaasFormExplainModel;
import com.xbongbong.paas.toolbox.exception.XbbException;
import com.xbongbong.paas.toolbox.helper.PageHelper;
import com.xbongbong.paas.toolbox.util.Arith;
import com.xbongbong.paas.toolbox.util.BeanUtil;
import com.xbongbong.paas.toolbox.util.CollectionsUtil;
import com.xbongbong.paas.toolbox.util.CommentUtil;
import com.xbongbong.paas.toolbox.util.JsonHelperUtil;
import com.xbongbong.paas.toolbox.util.PageHelperUtil;
import com.xbongbong.paas.toolbox.util.StringUtil;
import com.xbongbong.paas.toolbox.wrap.DepartmentVO;
import com.xbongbong.paas.toolbox.wrap.UserVO;
import com.xbongbong.paas.util.ExplainUtil;
import com.xbongbong.pro.domain.entity.ChartEntity;
import com.xbongbong.pro.enums.TimeTypeEnum;
import com.xbongbong.pro.enums.errorcodes.StatisticsServicesErrorCodeEnum;
import com.xbongbong.pro.enums.permission.enums.ViewProPermissionAliasEnum;
import com.xbongbong.pro.performanceset.pojo.PerformanceSetExplainPojo;
import com.xbongbong.pro.service.toolbox.statistic.util.ChartResultUtil;
import com.xbongbong.pro.statistic.constant.ChartI18Constant;
import com.xbongbong.pro.statistic.enums.NumFormatEnum;
import com.xbongbong.pro.statistic.enums.SystemCodeEnum;
import com.xbongbong.pro.statistic.enums.ValueTypeEnum;
import com.xbongbong.pro.statistic.pojo.vo.ContractInfoVO;
import com.xbongbong.pro.statistic.pojo.vo.ProductInfoVO;
import com.xbongbong.pro.statistic.result.pojo.AggsBucketsPojo;
import com.xbongbong.pro.statistic.result.pojo.ChartPkParamPojo;
import com.xbongbong.pro.statistic.result.pojo.ChartResultPkPojo;
import com.xbongbong.pro.statistic.result.pojo.ChartResultPojo;
import com.xbongbong.pro.statistic.result.pojo.ElasticAggsPojo;
import com.xbongbong.pro.statistic.result.pojo.TopResultPojo;
import com.xbongbong.pro.statistic.result.pojo.dto.ContractProductChartDTO;
import com.xbongbong.pro.statistic.result.pojo.dto.OutStockProductChartDTO;
import com.xbongbong.pro.statistic.result.pojo.dto.OutstockChartDTO;
import com.xbongbong.pro.statistic.result.pojo.dto.ProductCategoryChartDTO;
import com.xbongbong.pro.statistic.result.pojo.dto.ProductChartDTO;
import com.xbongbong.pro.statistic.result.pojo.dto.StatisticResultDTO;
import com.xbongbong.pro.statistic.result.pojo.dto.StatisticResultInfoDTO;
import com.xbongbong.pro.statistic.result.pojo.dto.StatisticResultPieInfoDTO;
import com.xbongbong.pro.statistic.result.pojo.vo.ChartResultDetailVO;
import com.xbongbong.pro.statistic.result.pojo.vo.ChartResultPkDetailVO;
import com.xbongbong.pro.statistic.result.pojo.vo.ResultDataSeriesChartVO;
import com.xbongbong.pro.statistic.result.pojo.vo.ResultDataTableVO;
import com.xbongbong.pro.statistic.result.pojo.vo.ResultTableVO;
import com.xbongbong.pro.statistic.through.pojo.dto.ThroughDTO;
import com.xbongbong.saas.constant.BasicConstant;
import com.xbongbong.saas.constant.PageConstant;
import com.xbongbong.saas.constant.StringConstant;
import com.xbongbong.saas.domain.entity.ContractProductEntity;
import com.xbongbong.saas.domain.entity.OutstockProductEntity;
import com.xbongbong.saas.domain.entity.ProductCategoryEntity;
import com.xbongbong.saas.enums.XbbRefTypeEnum;
import com.xbongbong.saas.enums.business.ContractEnum;
import com.xbongbong.saas.enums.business.OutstockEnum;
import com.xbongbong.saas.enums.business.ProductEnum;
import com.xbongbong.saas.enums.subform.ContractProductSubFormEnum;
import com.xbongbong.saas.enums.subform.ProductTeamEnum;
import com.xbongbong.saas.enums.subform.ProductWarehouseSubFormEnum;
import com.xbongbong.saas.model.OutstockProductModel;
import com.xbongbong.saas.model.ProductCategoryModel;
import com.xbongbong.saas.model.SpecificationModel;
import com.xbongbong.saas.service.toolbox.help.ProductHelp;
import com.xbongbong.saas.service.toolbox.help.SaasListDataPermissionHelp;
import com.xbongbong.saas.toolbox.help.SortHelp;
import com.xbongbong.sys.domain.entity.UserEntity;
import com.xbongbong.sys.model.DepartmentModel;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.join.query.HasChildQueryBuilder;
import org.elasticsearch.join.query.HasParentQueryBuilder;
import org.elasticsearch.join.query.JoinQueryBuilders;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.SumAggregationBuilder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;

import static com.xbongbong.paas.elasticsearch.constant.EsNormalConstant.MINI_MUM_SHOULD_MATCH;
import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
import static org.elasticsearch.index.query.QueryBuilders.existsQuery;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.index.query.QueryBuilders.termsQuery;

/**
 * 产品相关统计
 *
 * @author 徐俊杰
 * @version v1.0
 * @date 2019/3/5 10:00
 * @since v1.0
 */
@Service
public class CrmProductResult extends ChartResultParentService implements ChartResultSystem, ChartResultPk {
    @Resource
    private ProductHelp productHelp;
    @Resource
    private PaasAppModel paasAppModel;
    @Resource
    private OutstockProductModel outstockProductModel;
    @Resource
    private ProductCategoryModel productCategoryModel;
    @Resource
    private SpecificationModel specificationModel;
    @Resource
    private PaasFormExplainModel paasFormExplainModel;
    @Resource
    private SaasListDataPermissionHelp saasListDataPermissionHelp;
    @Resource
    private DepartmentModel departmentModel;
    @Resource
    private TransferSubFormHelper transferSubFormHelper;

    /**
     * 代表批量处理1000
     */
    private static final int batchSize = 1000;

    @Override
    public Object getTarget(ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO) throws XbbException {
        SystemCodeEnum systemCodeEnum = chartResultPojo.getSystemCodeEnum();
        switch (systemCodeEnum) {
            case PRODUCT_PROFIT_CHART:
                return productProfitChart(chartResultPojo, chartResultDetailVO);
            case PRODUCT_SALES_TOP:
                return productSalesTop(chartResultPojo, chartResultDetailVO);
            case PRODUCT_CATEGORY_RATE_CHART:
                return productCategoryRateChart(chartResultPojo, chartResultDetailVO);
            case PRODUCT_TABLE_CHART:
                return productTableChart(chartResultPojo, chartResultDetailVO);
            case PRODUCT_SALES_CHART:
                return productSalesChart(chartResultPojo, chartResultDetailVO);
            default:
                break;
        }
        return null;
    }

    @Override
    public Object getValueTarget(ChartResultPojo chartResultPojo) throws XbbException {
        SystemCodeEnum systemCodeEnum = chartResultPojo.getSystemCodeEnum();
        switch (systemCodeEnum) {
            case PRODUCT_COST_BOARD:
                return productCostBoard(chartResultPojo);
                //return "***";
            case PRODUCT_SALES_BOARD:
                return productSalesBoard(chartResultPojo);
            default:
                break;
        }
        return null;
    }

    @Override
    public Object getPkTarget(UserEntity userEntity, ChartResultPkPojo chartResultPkPojo, ChartResultPkDetailVO chartResultPkDetailVO) throws XbbException {
        SystemCodeEnum systemCodeEnum = chartResultPkPojo.getSystemCodeEnum();
        switch (systemCodeEnum) {
            //pk、完成情况（员工排行）
            case PK_PRODUCT_SALES:
            case PERFORMANCE_PRODUCT_SALES:
                return pkProductSales(userEntity, chartResultPkPojo, chartResultPkDetailVO);
                //return pkProductSalesNew(userEntity, chartResultPkPojo, chartResultPkDetailVO);
            default:
                break;
        }
        return null;
    }

    /**
     * 系统pk范围规则指标公共接口
     *
     * @param userEntity            登录员工
     * @param chartEntity
     * @param chartResultPkPojo     pk参数
     * @param chartResultPkDetailVO 塞入pk结果
     * @return java.lang.Object
     * @throws XbbException
     * @author zcp
     * @date 2019/3/18 15:34
     * @version v1.0
     * @since v1.0
     */
    @Override
    public Object getPkRuleTarget(UserEntity userEntity, ChartEntity chartEntity, ChartResultPkPojo chartResultPkPojo, ChartResultPkDetailVO chartResultPkDetailVO) throws XbbException {
        SystemCodeEnum systemCodeEnum = chartResultPkPojo.getSystemCodeEnum();
        switch (systemCodeEnum) {
            //pk、完成情况（员工排行）
            case PK_PRODUCT_SALES:
            case PERFORMANCE_PRODUCT_SALES:
                return pkRuleProductSales(userEntity, chartEntity, chartResultPkPojo, chartResultPkDetailVO);
            default:
                break;
        }
        return null;
    }

    /**
     * 产品毛利表
     *
     * @param chartResultPojo 入参
     * @param chartResultDetailVO 回参
     * @return java.lang.Object
     * @throws XbbException 调用的多个方法会抛出异常
     * @author 徐俊杰
     * @date 2019/4/1 19:30
     * @since v1.0
     */
    public Object productProfitChart(ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO) throws XbbException {
        String corpid = chartResultPojo.getCorpid();
        String unit = chartResultPojo.getUnit();
        Integer numFormat = chartResultPojo.getNumFormat();
        NumFormatEnum numFormatEnum = NumFormatEnum.getByCode(numFormat);
        //回参
        ResultTableVO resultTableVO = new ResultTableVO();
        //分页参数
        ChartResultUtil.formatPage(chartResultPojo, PageConstant.DEFAULT_PAGE_SIZE_SMALL);
        Integer page = chartResultPojo.getPage();
        Integer pageSize = chartResultPojo.getPageSize();
        //设置title
        List<String> title = Arrays.asList(String.format(I18nMessageUtil.getMessage(ChartI18Constant.PRODUCT_PROFIT_CHART_TITLE), unit, unit, unit).split(","));
        resultTableVO.setTitle(systemChartHelp.titleNumRight(title));
        //合同产品用到的集合
        ContractProductChartDTO contractProductChartDTO = contractProductDTO(chartResultPojo);
        Set<Long> productIdSet = contractProductChartDTO.getProductIdSet();
        //产品用到的集合
        ProductChartDTO productChartDTO = productDTO(corpid, productIdSet, page, pageSize);
        if (Objects.equals(BasicConstant.ZERO, productIdSet.size())) {
            productIdSet.add(-1L);
        }
        Map<Long, JSONObject> productIdDataMap = productChartDTO.getProductIdDataMap();
        Map<Long, String> parentIdNoMap = productChartDTO.getParentIdNoMap();
        //判断进销存是否开启
        boolean ifJxcUse = ifJXCOpen(corpid);
        List<List<ResultDataTableVO>> dataTableList = new ArrayList<>();
        Map<Long, Double> productIdSumPriceMap = contractProductChartDTO.getProductIdSumPriceMap();
        Map<Long, Double> productIdSumCostMap = contractProductChartDTO.getProductIdSumCostMap();
        //获取启用的产品单
        Map<String, String> unitMap = formHelp.getProductUnitItemMap(corpid);
        if (ifJxcUse) {
            //开启进销存：产品成本 = sum(出库成本*出库数量) + sum(合同产品成本*合同产品数量)
            Set<Long> contractIdSet = contractProductChartDTO.getContractIdSet();
            List<ContractProductEntity> contractProductList = contractProductChartDTO.getContractProductList();
            Map<String, Object> retMap = getProductCost(corpid, productIdSet, contractIdSet, contractProductList);
            //产品id--该产品的总出库成本
            Map<Long, Double> costMap4Out = (Map<Long, Double>) retMap.get("costMap4Out");
            //产品id--该产品的未出库总成本
            Map<Long, Double> costMap4No = (Map<Long, Double>) retMap.get("costMap4No");
            for (Map.Entry<Long, JSONObject> entry : productIdDataMap.entrySet()) {
                Long productId = entry.getKey();
                JSONObject product = productIdDataMap.get(productId);
                if (product == null) {
                    continue;
                }
                //产品成本金额=出库成本 + 未出库成本
                Double costOut = costMap4Out.get(productId);
                Double costNo = costMap4No.get(productId);
                double sumCost = (costOut == null ? 0 : costOut) + (costNo == null ? 0 : costNo);
                double sumPrice = productIdSumPriceMap.get(productId);
                //格式化产品信息
                List<ResultDataTableVO> dataList = formatObject(product, sumCost, sumPrice, parentIdNoMap, unitMap, numFormatEnum);
                dataTableList.add(dataList);
            }
        } else {
            //关闭进销存：产品成本 = sum(合同产品成本*合同产品数量)
            for (Map.Entry<Long, JSONObject> entry : productIdDataMap.entrySet()) {
                Long productId = entry.getKey();
                JSONObject product = productIdDataMap.get(productId);
                //格式化产品信息
                double sumCost = productIdSumCostMap.get(productId);
                double sumPrice = productIdSumPriceMap.get(productId);
                //格式化产品信息（一行）
                List<ResultDataTableVO> dataList = formatObject(product, sumCost, sumPrice, parentIdNoMap, unitMap, numFormatEnum);
                dataTableList.add(dataList);
            }
        }
        resultTableVO.setData(dataTableList);
        //返回值中设置pageHelper
        PageHelper pageHelper = productChartDTO.getPageHelper();
        resultTableVO.setPageHelper(pageHelper);
        resultTableVO.setTableName("产品毛利报表");
        chartResultDetailVO.setTable(resultTableVO);
        return chartResultDetailVO;
    }
    /**
     * 产品销量汇总表
     * @param chartResultPojo
     * @param chartResultDetailVO
     * @return java.lang.Object 
     * @throws XbbException
     * @author zcp
     * @date 2019/4/10 16:53
     * @since v1.0
     * @version v1.0
     */
    public Object productTableChart(ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO) throws XbbException {
        ChartResultUtil.formatPage(chartResultPojo, PageConstant.DEFAULT_PAGE_SIZE_SMALL);
        Integer page = chartResultPojo.getPage();
        Integer pageSize = chartResultPojo.getPageSize();
        String corpid = chartResultPojo.getCorpid();
        //先查出所有销售合同产品
        BoolQueryBuilder contractProductQueryBuilder = chartQueryHelp.contractProductQuery(chartResultPojo, null);
        List<String> fieldList = Arrays.asList(ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.PRODUCT_ID), ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.PRODUCT_NUM), ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.PRICE));
        List<PaasFormDataEntity> contractProductlist = esHelper.findByScroll(IndexTypeEnum.IDX_SAAS_CONTRACT_PRODUCT, contractProductQueryBuilder, PaasFormDataEntity.class, fieldList);
        List<ContractProductEntity> contractProductEntities = transferSubFormHelper.transferFormDataToContractProductList(contractProductlist);
        //产品id--产品数量
        Map<Long, Double> idNumMap = new HashMap<>(contractProductlist.size());
        //产品id--产品数量*售价
        Map<Long, Double> idNumPriceMap = new HashMap<>(contractProductlist.size());
        //得到销售的产品ids，并把所有的销量、销售额汇总
        Set<Long> productIdIn = formatSales(contractProductEntities, idNumMap, idNumPriceMap);
        //限制产品ids
        BoolQueryBuilder productQueryBuilder = chartQueryHelp.boolQueryBuilder4BaseByIds(corpid, null, productIdIn, IndexTypeEnum.IDX_SAAS_PRODUCT);
        //处理分类筛选
        Long categoryId = chartResultPojo.getRefId();
        //分类id-分类名map
        Map<Long, String> categoryIdNameMap = productHelp.setCategoryQuery(corpid, productQueryBuilder, categoryId);
        //分页查询
        XbbAggregatedPage<ProductInfoVO> esEntities = esHelper.findByPage(IndexTypeEnum.IDX_SAAS_PRODUCT, productQueryBuilder, ProductInfoVO.class, null, page, pageSize, null);
        List<ProductInfoVO> productInfoVoList = new ArrayList<>();
        int rowCounts = 0;
        if (esEntities != null) {
            productInfoVoList = esEntities.getContent();
            rowCounts = (int) esEntities.getTotalElements();
        }
        PageHelper pageHelper = PageHelperUtil.initPageHelper(rowCounts, page, pageSize, PageConstant.DEFAULT_PAGE_SIZE_SMALL);
        //封装产品销量表数据
        List<List<ResultDataTableVO>> tableData = formatProductSaleTableData(corpid, idNumMap, idNumPriceMap, categoryIdNameMap, productInfoVoList, chartResultPojo);
        ResultTableVO table = new ResultTableVO();
        table.setPageHelper(pageHelper);
        List<String> title = Arrays.asList(String.format(I18nMessageUtil.getMessage(ChartI18Constant.PRODUCT_SUMMARY_SALES_CHART_TITLE), chartResultPojo.getUnit()).split(","));
        table.setTitle(systemChartHelp.titleNumRight(title));
        table.setData(tableData);
        table.setTableName("产品销售汇总表");
        chartResultDetailVO.setTable(table);
        return chartResultDetailVO;
    }

    /**
     * 单个产品销量
     *
     * @param chartResultPojo 入参
     * @param chartResultDetailVO 回参
     * @return java.lang.Object
     * @throws XbbException
     * @author 徐俊杰
     * @date 2019/4/26 9:26
     * @since v1.0
     */
    private Object productSalesChart(ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO) throws XbbException {
        List<String> legendList = Arrays.asList(I18nMessageUtil.getMessage(ChartI18Constant.PRODUCT_SALES_SINGLE_CHART_TITLE).split(","));
        if (TimeTypeEnum.MONTH.getCode() == chartResultPojo.getFiscalYearTimePojo().getTimeType4DefaultMonth()) {
            throw new XbbException(StatisticsServicesErrorCodeEnum.API_ERROR_224007, String.format(StatisticsServicesErrorCodeEnum.API_ERROR_224007.getMsg(), "，产品销量趋势分析因有环比不能筛选月份"));
        }
        List<String> unitList = Arrays.asList("", "", "", "%", "%");
        Long productId = chartResultPojo.getRefId();
        StatisticResultDTO statisticResultDTO;
        //判断产品id是否存在，若不存在，即还没有筛选产品则返回空值
        if (productId == null) {
            chartResultDetailVO.setChart(new ArrayList<>());
            chartResultDetailVO.setTable(new ResultTableVO());
            return chartResultDetailVO;
        }
        statisticResultDTO = getStatisticResultDto4MomYoy(chartResultPojo, legendList, unitList);
        statisticResultDTO.setTotalFlag(true);
        statisticResultDTO.setValueTitle("当前产品销量");
        return systemChartHelp.packageResult(statisticResultDTO, chartResultPojo, chartResultDetailVO);
    }

    /**
     * 当前库存成本
     *
     * @param chartResultPojo corpid
     * @return java.lang.String
     * @throws XbbException 查询产品抛出的异常
     * @author 徐俊杰
     * @date 2019/3/31 11:00
     * @since v1.0
     */
//    public String productCostBoard(ChartResultPojo chartResultPojo) throws XbbException {
//        //首页库存成本 不可见展示为****
//        UserVO userVO = chartResultPojo.getLoginUser();
//        Set<String> permSet = userVO.getPermSet();
//        if (!permSet.contains(ViewProPermissionAliasEnum.PRODUCT_VIEW.getAlias()) || !permSet.contains(ViewProPermissionAliasEnum.WAREHOUSE_VIEW.getAlias())) {
//            return StringConstant.INVISIBLE_VALUE;
//        }
//        PaasFormExplainEntity paasFormExplainEntity = paasFormExplainModel.getByBusinessType(XbbRefTypeEnum.PRODUCT.getCode(), chartResultPojo.getCorpid());
//        if(paasFormExplainEntity!=null){
//            List<FieldAttrEntity> explainList = JSONArray.parseArray(paasFormExplainEntity.getExplains(), FieldAttrEntity.class);
//            for (FieldAttrEntity fieldAttrEntity:explainList){
//                if(ProductEnum.COST.getAttr().equals(fieldAttrEntity.getAttr())){
//                    if(fieldAttrEntity.getIsOpen()==0||fieldAttrEntity.getVisible()==0){
//                        return StringConstant.INVISIBLE_VALUE;
//                    }
//                }
//            }
//        }
//        BoolQueryBuilder boolQueryBuilder = chartQueryHelp.productCostQuery(chartResultPojo);
//        double totalCost = systemChartHelp.getAgg4ProductCost(boolQueryBuilder);
//        return ChartResultUtil.formatValue2Double(false, ValueTypeEnum.VALUE_MONEY, chartResultPojo.isThousandSwitch(), totalCost);
//    }
    /**
     * @Description 当前库存成本
     * @param chartResultPojo
     * @return java.lang.String
     * @throws XbbException 查询产品抛出的异常
     * @author weiming
     * @Date  2020/10/27/16:56
     * @since v2.0
     **/
    public String productCostBoard(ChartResultPojo chartResultPojo) throws XbbException {
        //首页库存成本 不可见展示为****
        UserVO userVO = chartResultPojo.getLoginUser();
        Set<String> permSet = userVO.getPermSet();
        boolean maxDataPermission = userVO.isMaxDataPermission(true);
        if (!maxDataPermission && (!permSet.contains(ViewProPermissionAliasEnum.PRODUCT_VIEW.getAlias()) || !permSet.contains(ViewProPermissionAliasEnum.WAREHOUSE_VIEW.getAlias()))) {
            return StringConstant.INVISIBLE_VALUE;
        }
        PaasFormExplainEntity paasFormExplainEntity = paasFormExplainModel.getByBusinessType(XbbRefTypeEnum.PRODUCT.getCode(), chartResultPojo.getCorpid());
        //字段高级设置指标
        int tempParam = 0;
        //成本字段的解释
        List<Integer> costFieldRelative = new ArrayList<>();
        if (!maxDataPermission && paasFormExplainEntity != null) {
            List<FieldAttrEntity> explainList = JSONArray.parseArray(paasFormExplainEntity.getExplains(), FieldAttrEntity.class);
            for (FieldAttrEntity fieldAttrEntity : explainList) {
                if (ProductEnum.COST.getAttr().equals(fieldAttrEntity.getAttr())) {
                    //判断成本字段是否启用、是否开启可见权限
                    if (fieldAttrEntity.getIsOpen() == 0 || fieldAttrEntity.getVisible() == 0) {
                        return StringConstant.INVISIBLE_VALUE;
                    }
                    costFieldRelative = fieldAttrEntity.getVisibleScopeRule().getRelative();
                    //成本字段启用
                    tempParam = ExplainUtil.costFieldInvisibleOfScope(fieldAttrEntity, userVO);
                    if (tempParam == 4) {
                        return StringConstant.INVISIBLE_VALUE;
                    }
                    break;
                }
            }
        }
        //成本字段的权限已得到，接着查询仓库的权限
        //查询当前用户拥有哪些仓库的权限
        List<Long> allWarehouseIdList = getWarehouseIdsByPermission(userVO,userVO.getDepSet());
        if (CollectionUtils.isEmpty(allWarehouseIdList)) {
            return StringConstant.INVISIBLE_VALUE;
        }
        //拼装产品团队的权限，本人userId和本人所在部门以及父部门
        List<List<ConditionsEntityExt>> shouldConditions = new ArrayList<>();
        //组装仓库的权限（包括哪些仓库）
        List<ConditionsEntityExt> conditionsEntityExtList = new ArrayList<>();
        BoolQueryBuilder boolQueryBuilder = boolQuery();
        if (!userVO.isMaxDataPermission()){
            if (allWarehouseIdList.isEmpty()){
                allWarehouseIdList.add(-1L);
            }
            conditionsEntityExtList.add(new ConditionsEntityExt(ProductEnum.WAREHOUSE.getAttr(),"",ProductEnum.WAREHOUSE.getFieldType(), ConditionEnum.INCLUDE.getSymbol(),new ArrayList<>(allWarehouseIdList)));
            boolQueryBuilder.filter(termsQuery(ProductWarehouseSubFormEnum.getEsAttr(ProductWarehouseSubFormEnum.WAREHOUSE_ID),allWarehouseIdList));
        } else {
            conditionsEntityExtList.add(new ConditionsEntityExt(ProductEnum.WAREHOUSE.getAttr(),"",ProductEnum.WAREHOUSE.getFieldType(),ConditionEnum.NOEMPTY.getSymbol(),new ArrayList<>()));
        }
        //不管是不是超管，都要查询当前用户的父部门
        List<Long> depIdIn = new ArrayList<>();
        Set<DepartmentVO> depSet = userVO.getDepSet();
        //本人部门
        for (DepartmentVO departmentVO : depSet){
            depIdIn.add(departmentVO.getId());
        }
        //父部门
        List<Long> supDepIdIn = departmentModel.getSupDepIdList(userVO.getCorpid(), depIdIn, true);
        //本人userId和本人所在部门以及父部门封装到shouldConditions
        saasListDataPermissionHelp.listDepAndUserDataPermission(shouldConditions, userVO, XbbRefTypeEnum.PRODUCT.getCode(), userVO.getDepSet());
        //查所有的子产品
        conditionsEntityExtList.add(new ConditionsEntityExt(ProductEnum.PARENT_ID.getAttr(),"",ProductEnum.PARENT_ID.getFieldType(),ConditionEnum.NOEQUAL.getSymbol(), Collections.singletonList(0)));
        BoolQueryBuilder parentBoolQueryBuilder = boolQuery();
        parentBoolQueryBuilder.filter(termQuery("corpid.keyword",chartResultPojo.getCorpid()));
        parentBoolQueryBuilder.filter(termQuery(StringConstant.DEL,DelEnum.NORMAL.getDel()));
        boolQueryBuilder.filter(termQuery("corpid.keyword",chartResultPojo.getCorpid()));
        boolQueryBuilder.filter(termQuery(StringConstant.DEL, DelEnum.NORMAL.getDel()));
        boolQueryBuilder.mustNot(termQuery(ProductWarehouseSubFormEnum.getEsAttr(ProductWarehouseSubFormEnum.WAREHOUSE_ID),0));
        boolQueryBuilder.filter(termQuery(ProductWarehouseSubFormEnum.getAttrConnectData(ProductWarehouseSubFormEnum.WAREHOUSE_CHECKED),1));
        if (!maxDataPermission) {
            //负责人（人员）
            BoolQueryBuilder child1QueryBuilder = boolQuery();
            child1QueryBuilder.filter(termQuery(ProductTeamEnum.getEsAttr(ProductTeamEnum.RELATION_ID), userVO.getUserId()));
            child1QueryBuilder.filter(termQuery(ProductTeamEnum.getEsAttr(ProductTeamEnum.TYPE), 0));
            //负责人（部门）
            BoolQueryBuilder child2QueryBuilder = boolQuery();
            child2QueryBuilder.filter(termsQuery(ProductTeamEnum.getEsAttr(ProductTeamEnum.RELATION_ID), supDepIdIn));
            child2QueryBuilder.filter(termQuery(ProductTeamEnum.getEsAttr(ProductTeamEnum.TYPE), 1));
            BoolQueryBuilder childQueryBuilder = boolQuery();
            childQueryBuilder.filter(termQuery("corpid.keyword", chartResultPojo.getCorpid()));
            childQueryBuilder.filter(termQuery(StringConstant.DEL, DelEnum.NORMAL.getDel()));
            childQueryBuilder.should(child1QueryBuilder);
            childQueryBuilder.should(child2QueryBuilder);
            childQueryBuilder.minimumShouldMatch(1);
            HasChildQueryBuilder hasChild2QueryBuilder = JoinQueryBuilders.hasChildQuery(IndexTypeEnum.IDX_SAAS_PRODUCT_TEAM.getType(), childQueryBuilder, ScoreMode.Max);
            switch (tempParam){
                case 1:
                    parentBoolQueryBuilder.filter(hasChild2QueryBuilder);
                    break;
                case 2:
                    //勾选了负责人或者创建人。-查询时要加上负责人或者创建人 负责人不等于该用户&&创建人不等于该用户
                    //负责人是1 创建人是3 负责人在es中的type为1
                    if (costFieldRelative.contains(1)) {
                        parentBoolQueryBuilder.mustNot(hasChild2QueryBuilder);
                    }
                    if (costFieldRelative.contains(3)) {
                        //创建人
                        parentBoolQueryBuilder.mustNot(termQuery("creatorId.keyword",userVO.getUserId()));
                    }
                    break;
                case 3:
                    //选了负责人、创建人。查询时要加上should条件，负责人、创建人等于一个就行
                    if (costFieldRelative.contains(1)) {
                        parentBoolQueryBuilder.should(hasChild2QueryBuilder);
                    }
                    if (costFieldRelative.contains(3)) {
                        //创建人
                        parentBoolQueryBuilder.should(termQuery("creatorId.keyword", userVO.getUserId()));
                    }
                    parentBoolQueryBuilder.minimumShouldMatch(MINI_MUM_SHOULD_MATCH);
                    break;
                default:
                    break;
            }
        }
        EsUtil.parseCondition(parentBoolQueryBuilder,conditionsEntityExtList,"",chartResultPojo.getCorpid(),XbbRefTypeEnum.PRODUCT.getCode());
        //EsUtil.parseShouldCondition(parentBoolQueryBuilder,shouldConditions,"and",chartResultPojo.getCorpid(),XbbRefTypeEnum.PRODUCT.getCode())
        HasParentQueryBuilder hasParentQueryBuilder = JoinQueryBuilders.hasParentQuery(IndexTypeEnum.IDX_SAAS_PRODUCT.getType(), parentBoolQueryBuilder, false);
        boolQueryBuilder.filter(hasParentQueryBuilder);
        SumAggregationBuilder sumAggregationBuilder = AggregationBuilders.sum("cost").script(new Script("doc['"+ProductWarehouseSubFormEnum.getEsAttr(ProductWarehouseSubFormEnum.NUM)+"'].value * doc['"+ProductWarehouseSubFormEnum.getEsAttr(ProductWarehouseSubFormEnum.COST)+"'].value"));
        boolQueryBuilder.filter(existsQuery(ProductWarehouseSubFormEnum.getEsAttr(ProductWarehouseSubFormEnum.NUM)));
        boolQueryBuilder.filter(existsQuery(ProductWarehouseSubFormEnum.getEsAttr(ProductWarehouseSubFormEnum.COST)));
        JSONObject jsonObject = esHelper.elasticAggs4NoBuckets2Object(IndexTypeEnum.IDX_SAAS_PRODUCT_WAREHOUSE, boolQueryBuilder, sumAggregationBuilder);
        JSONObject aggregations = jsonObject.getJSONObject("aggregations");
        Double value = aggregations.getJSONObject("cost").getDouble("value");
        //保留2位小数
        double total = Arith.div(value, 1, 2);
        return ChartResultUtil.formatValue2Double(false, ValueTypeEnum.VALUE_MONEY, chartResultPojo.isThousandSwitch(), total, NumFormatEnum.getByCode(chartResultPojo.getNumFormat()), chartResultPojo.getNumFormatFlag());
    }

    public List<Long> getWarehouseIdsByPermission(UserVO loginUser, Set<DepartmentVO> depSet) throws XbbException {
        String corpid = loginUser.getCorpid();
        BoolQueryBuilder boolQueryBuilder = boolQuery();
        boolQueryBuilder.filter(termQuery("corpid.keyword",corpid));
        boolQueryBuilder.filter(termQuery(StringConstant.DEL,DelEnum.NORMAL.getDel()));
        if (!loginUser.isMaxDataPermission(true)){
            List<List<ConditionsEntityExt>> shouldConditionList = new ArrayList<>();
            saasListDataPermissionHelp.listDepAndUserDataPermission(shouldConditionList, loginUser,XbbRefTypeEnum.WAREHOUSE.getCode(),depSet);
            EsUtil.parseShouldCondition(boolQueryBuilder,shouldConditionList,"",corpid,XbbRefTypeEnum.WAREHOUSE.getCode());
        }
        List<PaasFormDataEntityExt> paasFormDataEntityExtList = esHelper.findByScroll(IndexTypeEnum.IDX_SAAS_WAREHOUSE,boolQueryBuilder,PaasFormDataEntityExt.class, Collections.singletonList(FieldTypeEnum.DATAID.getAlias()));
        List<Long> dataIdList = new ArrayList<>();
        for (PaasFormDataEntityExt entityExt : paasFormDataEntityExtList){
            dataIdList.add(entityExt.getDataId());
        }
        return dataIdList;
    }
    /**
     * 产品销量（所有产品）
     * @param chartResultPojo
     * @return java.lang.String 
     * @throws XbbException
     * @author zcp
     * @date 2019/3/29 11:44
     * @since v1.0
     * @version v1.0
     */
    public String productSalesBoard(ChartResultPojo chartResultPojo) throws XbbException {
        BoolQueryBuilder productQueryBuilder = chartQueryHelp.contractProductQuery(chartResultPojo, null);
        String sumField = ContractProductSubFormEnum.getEsAttr(ContractProductSubFormEnum.PRODUCT_NUM);
        if (chartResultPojo.getPerformancePk()) {
            PerformanceSetExplainPojo explainPojo = performanceHelp.getPerformanceSetExplainPojo(chartResultPojo.getChartEntity());
            List<Long> assessProductList = explainPojo.getAccessProductIds();
            //如果业绩目标，则过滤设置过的考核产品
            if (assessProductList != null && assessProductList.size() > 0) {
                productQueryBuilder.filter(termsQuery(ContractProductSubFormEnum.getEsAttr(ContractProductSubFormEnum.PRODUCT_ID), assessProductList));
            }
        }
        double total = systemChartHelp.getDouble4Total(IndexTypeEnum.IDX_SAAS_CONTRACT_PRODUCT, productQueryBuilder, sumField);
        return StringUtil.formatDouble(total, "###0.00");
    }

    /**
     * 按照聚合方式得到产品销量pk
     * @param userEntity
     * @param chartResultPkPojo
     * @param chartResultPkDetailVO
     * @return com.xbongbong.pro.statistic.result.pojo.vo.ChartResultPkDetailVO
     * @throws XbbException
     * @author zcp
     * @date 19/8/20 020 16:13
     * @update 19/8/20 020 16:13
     * @since v1.0
     * @version v1.0
     */
    public ChartResultPkDetailVO pkProductSalesNew(UserEntity userEntity, ChartResultPkPojo chartResultPkPojo, ChartResultPkDetailVO chartResultPkDetailVO) throws XbbException {
        IndexTypeEnum indexTypeEnum = IndexTypeEnum.IDX_SAAS_CONTRACT;
        //塞入合同查询条件：不限制签订人、不限制红冲
        BoolQueryBuilder productSalesQuery = chartQueryHelp.productSalesSingleQuery4Parent(false, null, chartResultPkPojo);

        String fieldGroup = ContractEnum.getEsAttr4Keyword(ContractEnum.SIGN_PERSON);
        String childType = IndexTypeEnum.IDX_SAAS_CONTRACT_PRODUCT.getType();
        String sumField = ContractProductSubFormEnum.getEsAttr(ContractProductSubFormEnum.PRODUCT_NUM);
        int pkType = chartResultPkPojo.getPkType();
        chartResultPkPojo.setValueTypeEnum(ValueTypeEnum.VALUE_DOUBLE);
        return pkHelp.getPkResult4ProductSales(userEntity, chartResultPkPojo, chartResultPkDetailVO, indexTypeEnum, productSalesQuery, fieldGroup, childType, sumField, pkType);
    }

    /**
     * 产品销量pk
     * @param userEntity
     * @param chartResultPkPojo
     * @param chartResultPkDetailVO
     * @return com.xbongbong.pro.statistic.pojo.vo.ChartResultPkDetailVO
     * @throws XbbException
     * @author zcp
     * @date 2019/3/29 11:10
     * @since v1.0
     * @version v1.0
     */
    public ChartResultPkDetailVO pkRuleProductSales(UserEntity userEntity, ChartEntity chartEntity, ChartResultPkPojo chartResultPkPojo, ChartResultPkDetailVO chartResultPkDetailVO) throws XbbException {
        //塞入合同查询条件：是否限制签订人根据signUserFlag、不限制红冲
        BoolQueryBuilder contractQueryBuilder = chartRuleQueryHelp.contractQuery4LimitStart(true, null, chartEntity, chartResultPkPojo);
        /*--------此时查出的合同只限制了签订时间--------*/
        List<PaasFormDataEntityExt> contractList = formHelp.getContractList(contractQueryBuilder);
        //筛选签订时间 --筛选合同id（因为关联表无时间、无userId，故筛选时间和筛选部门(员工)只能根据合同）
        List<Long> contractIdIn = new ArrayList<>();
        //将合同产品列表中的合同签订人扩展字段进行填充--不用连表，获取到list后再把签订人置上，提高效率
        Map<Long, ContractInfoVO> contractMap = new HashMap<>(contractList.size());
        String signPersonAttr = ContractEnum.SIGN_PERSON.getAttr();
        for (PaasFormDataEntityExt entity : contractList) {
            Long contractId = entity.getDataId();
            contractIdIn.add(contractId);
            JSONObject data = entity.getData();
            ContractInfoVO contractInfoVO = new ContractInfoVO();
            contractInfoVO.setId(contractId);
            if (data != null) {
                contractInfoVO.setSignUserId(data.getString(signPersonAttr));
            }
            contractMap.put(contractId, contractInfoVO);
        }

        List<String> fieldList = Arrays.asList(ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.CONTRACT_ID), ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.PRODUCT_NUM));
        //list内部为JSONObject，格式为：uid--该员工的总额
        List<JSONObject> resultList = new ArrayList<>();
        //userId---员工业务总额
        Map<String, Double> numUserMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        //此时的contractIdIn只限制了签订时间（非pk榜的还会限制组织架构）
        contractIdIn.add(-1L);
        //对合同订单id进行一个分批处理
        if (CollectionsUtil.isNotEmpty(contractIdIn)) {
            long dataSize = contractIdIn.size();
            //代表合同订单的总数大于分批写死的数量
            if (batchSize < dataSize) {
                long part = dataSize/batchSize;
                long remainder = dataSize % batchSize;
                for (int i = 0; i <= part; i++) {
                    List<Long> listPage = new ArrayList<>();
                    if (i == part && remainder > 0) {
                        //代表是最后一批
                        listPage = contractIdIn.subList(i*batchSize,contractIdIn.size());
                    } else {
                        listPage = contractIdIn.subList(i*batchSize,(i+1)*(batchSize));
                    }
                    /*--------根据限制了签订时间的合同ids查询合同产品：pk榜不限制签订人--------*/
                    BoolQueryBuilder productQueryBuilder = chartRuleQueryHelp.newProductContractQuery(chartResultPkPojo.getCorpid(), listPage, chartResultPkPojo);
                    List<PaasFormDataEntity> contractProductlist = esHelper.findByScroll(IndexTypeEnum.IDX_SAAS_CONTRACT_PRODUCT, productQueryBuilder, PaasFormDataEntity.class, fieldList);
                    List<ContractProductEntity> contractProductEntities = transferSubFormHelper.transferFormDataToContractProductList(contractProductlist);
                    for (ContractProductEntity entity : contractProductEntities) {
                        Long contractId = entity.getContractId();
                        ContractInfoVO contractInfoVo = contractMap.get(contractId);
                        if (contractInfoVo == null) {
                            continue;
                        }
                        String signUserId  = contractInfoVo.getSignUserId();
                        Double value = numUserMap.get(signUserId);
                        if (value == null) {
                            value = 0D;
                        }
                        value += entity.getProductNum();
                        //更新员工合同产品数量
                        numUserMap.put(signUserId, value);
                    }
                }
            }else {
                BoolQueryBuilder productQueryBuilder = chartRuleQueryHelp.newProductContractQuery(chartResultPkPojo.getCorpid(), contractIdIn, chartResultPkPojo);
                List<PaasFormDataEntity> contractProductlist = esHelper.findByScroll(IndexTypeEnum.IDX_SAAS_CONTRACT_PRODUCT, productQueryBuilder, PaasFormDataEntity.class, fieldList);
                List<ContractProductEntity> contractProductEntities = transferSubFormHelper.transferFormDataToContractProductList(contractProductlist);
                for (ContractProductEntity entity : contractProductEntities) {
                    Long contractId = entity.getContractId();
                    ContractInfoVO contractInfoVo = contractMap.get(contractId);
                    if (contractInfoVo == null) {
                        continue;
                    }
                    String signUserId  = contractInfoVo.getSignUserId();
                    Double value = numUserMap.get(signUserId);
                    if (value == null) {
                        value = 0D;
                    }
                    value += entity.getProductNum();
                    //更新员工合同产品数量
                    numUserMap.put(signUserId, value);
                }
            }
        }

        for (Map.Entry<String, Double> entry : numUserMap.entrySet()) {
            JSONObject resultObject = new JSONObject();
            resultObject.put("userId", entry.getKey());
            resultObject.put("value", entry.getValue());
            resultList.add(resultObject);
        }
        int pkType = chartResultPkPojo.getPkType();
        chartResultPkPojo.setValueTypeEnum(ValueTypeEnum.VALUE_DOUBLE);
        ChartPkParamPojo chartPkParamPojo = chartDtoHelp.getChartPkRuleParamPojo(pkType, userEntity, chartEntity, chartResultPkPojo);
        return pkRuleHelp.getPkResult4DoubleByResultList(chartPkParamPojo, userEntity, chartResultPkPojo, chartResultPkDetailVO, pkType, resultList);
    }

    /**
     * 产品销量pk
     * @param userEntity
     * @param chartResultPkPojo
     * @param chartResultPkDetailVO
     * @return com.xbongbong.pro.statistic.pojo.vo.ChartResultPkDetailVO
     * @throws XbbException
     * @author zcp
     * @date 2019/3/29 11:10
     * @since v1.0
     * @version v1.0
     */
    public ChartResultPkDetailVO pkProductSales(UserEntity userEntity, ChartResultPkPojo chartResultPkPojo, ChartResultPkDetailVO chartResultPkDetailVO) throws XbbException {
        //塞入合同查询条件：是否限制签订人根据signUserFlag、不限制红冲
        BoolQueryBuilder contractQueryBuilder = chartQueryHelp.contractQuery4LimitStart(false, null, chartResultPkPojo);
        /*--------此时查出的合同只限制了签订时间--------*/
        List<PaasFormDataEntityExt> contractList = formHelp.getContractList(contractQueryBuilder);
        //筛选签订时间 --筛选合同id（因为关联表无时间、无userId，故筛选时间和筛选部门(员工)只能根据合同）
        List<Long> contractIdIn = new ArrayList<>();
        //将合同产品列表中的合同签订人扩展字段进行填充--不用连表，获取到list后再把签订人置上，提高效率
        Map<Long, ContractInfoVO> contractMap = new HashMap<>(contractList.size());
        String signPersonAttr = ContractEnum.SIGN_PERSON.getAttr();
        for (PaasFormDataEntityExt entity : contractList) {
            Long contractId = entity.getDataId();
            contractIdIn.add(contractId);
            JSONObject data = entity.getData();
            ContractInfoVO contractInfoVO = new ContractInfoVO();
            contractInfoVO.setId(contractId);
            if (data != null) {
                contractInfoVO.setSignUserId(data.getString(signPersonAttr));
            }
            contractMap.put(contractId, contractInfoVO);
        }

        List<String> fieldList = Arrays.asList(ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.CONTRACT_ID), ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.PRODUCT_NUM));
        //list内部为JSONObject，格式为：uid--该员工的总额
        List<JSONObject> resultList = new ArrayList<>();
        //userId---员工业务总额
        Map<String, Double> numUserMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        //此时的contractIdIn只限制了签订时间（非pk榜的还会限制组织架构）
        contractIdIn.add(-1L);
        //对合同订单id进行一个分批处理
        if (CollectionsUtil.isNotEmpty(contractIdIn)) {
             long dataSize = contractIdIn.size();
            //代表合同订单的总数大于分批写死的数量
             if (batchSize < dataSize) {
                 long part = dataSize/batchSize;
                 long remainder = dataSize % batchSize;
                 for (int i = 0; i <= part; i++) {
                     List<Long> listPage = new ArrayList<>();
                     if (i == part && remainder > 0) {
                         //代表是最后一批
                         listPage = contractIdIn.subList(i*batchSize,contractIdIn.size());
                     } else {
                         listPage = contractIdIn.subList(i*batchSize,(i+1)*(batchSize));
                     }
                     /*--------根据限制了签订时间的合同ids查询合同产品：pk榜不限制签订人--------*/
                     BoolQueryBuilder productQueryBuilder = chartQueryHelp.newProductContractQuery(chartResultPkPojo.getCorpid(), listPage, chartResultPkPojo);
                     List<PaasFormDataEntity> contractProductlist = esHelper.findByScroll(IndexTypeEnum.IDX_SAAS_CONTRACT_PRODUCT, productQueryBuilder, PaasFormDataEntity.class, fieldList);
                     List<ContractProductEntity> contractProductEntities = transferSubFormHelper.transferFormDataToContractProductList(contractProductlist);
                     for (ContractProductEntity entity : contractProductEntities) {
                         Long contractId = entity.getContractId();
                         ContractInfoVO contractInfoVo = contractMap.get(contractId);
                         if (contractInfoVo == null) {
                             continue;
                         }
                         String signUserId  = contractInfoVo.getSignUserId();
                         Double value = numUserMap.get(signUserId);
                         if (value == null) {
                             value = 0D;
                         }
                         value += entity.getProductNum();
                         //更新员工合同产品数量
                         numUserMap.put(signUserId, value);
                     }
                 }
             }else {
                 BoolQueryBuilder productQueryBuilder = chartQueryHelp.newProductContractQuery(chartResultPkPojo.getCorpid(), contractIdIn, chartResultPkPojo);
                 List<PaasFormDataEntity> contractProductlist = esHelper.findByScroll(IndexTypeEnum.IDX_SAAS_CONTRACT_PRODUCT, productQueryBuilder, PaasFormDataEntity.class, fieldList);
                 List<ContractProductEntity> contractProductEntities = transferSubFormHelper.transferFormDataToContractProductList(contractProductlist);
                 for (ContractProductEntity entity : contractProductEntities) {
                     Long contractId = entity.getContractId();
                     ContractInfoVO contractInfoVo = contractMap.get(contractId);
                     if (contractInfoVo == null) {
                         continue;
                     }
                     String signUserId  = contractInfoVo.getSignUserId();
                     Double value = numUserMap.get(signUserId);
                     if (value == null) {
                         value = 0D;
                     }
                     value += entity.getProductNum();
                     //更新员工合同产品数量
                     numUserMap.put(signUserId, value);
                 }
             }
         }

        for (Map.Entry<String, Double> entry : numUserMap.entrySet()) {
            JSONObject resultObject = new JSONObject();
            resultObject.put("userId", entry.getKey());
            resultObject.put("value", entry.getValue());
            resultList.add(resultObject);
        }
        int pkType = chartResultPkPojo.getPkType();
        chartResultPkPojo.setValueTypeEnum(ValueTypeEnum.VALUE_DOUBLE);
        ChartPkParamPojo chartPkParamPojo = chartDtoHelp.getChartPkParamPojo(pkType, userEntity, chartResultPkPojo);
        return pkHelp.getPkResult4DoubleByResultList(chartPkParamPojo, userEntity, chartResultPkPojo, chartResultPkDetailVO, pkType, resultList);
    }

    /**
     * 产品销量排行榜
     *
     * @param chartResultPojo     入参
     * @param chartResultDetailVO 出参
     * @return java.lang.Object
     * @throws XbbException findByScroll时抛出的异常
     * @author 徐俊杰
     * @date 2019/4/2 15:25
     * @since v1.0
     */
    private Object productSalesTop(ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO) throws XbbException {
        Integer isRed = null;
        String seriesName = "产品销量";
        return contractProductTop(chartResultPojo, chartResultDetailVO, isRed, seriesName,false);
    }

    /**
     * 产品分类销量
     *
     * @param chartResultPojo     入参
     * @param chartResultDetailVO 出参
     * @return java.lang.Object
     * @throws XbbException findByScroll时抛出的异常
     * @author 徐俊杰
     * @date 2019/4/8 17:08
     * @since v1.0
     */
    private Object productCategoryRateChart(ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO) throws XbbException {
        String corpid = chartResultPojo.getCorpid();
        Long selectCategoryId = chartResultPojo.getRefId();
        selectCategoryId = selectCategoryId == null ? Long.valueOf(0) : selectCategoryId;
        //处理合同产品
        ElasticAggsPojo contractProductAgg = productCategoryRateAgg(chartResultPojo);
        ContractProductChartDTO contractProductChartDTO = productCategoryRateDTO(contractProductAgg);
        Set<Long> productIdSet = contractProductChartDTO.getProductIdSet();
        Map<Long, Double> productIdNumMap = contractProductChartDTO.getProductIdNumMap();
        //查询产品
        IndexTypeEnum formIndexTypeEnum = IndexTypeEnum.IDX_SAAS_PRODUCT;
        ProductEnum categoryIdEnum = ProductEnum.CATEGORY_ID;
        String categoryIdAttr = categoryIdEnum.getAttr();
        List<String> fieldList = Arrays.asList("dataId", ProductEnum.getAttrConnectData(categoryIdEnum));
        List<PaasFormDataEntityExt> productList = formHelp.getFormListByIds4All(corpid, formIndexTypeEnum, productIdSet, fieldList);
        //分类id--产品productIdList
        Map<Long, Set<Long>> categoryProductIdsMap = new HashMap<>(productList.size());
        //是否有无分类的产品
        boolean ifOther = false;
        for (PaasFormDataEntityExt entityExt : productList) {
            JSONObject data = entityExt.getData();
            if (data == null) {
                continue;
            }
            Long dataId = entityExt.getDataId();
            long categoryId = data.getLongValue(categoryIdAttr);
            //该分类下的产品
            Set<Long> productIds = categoryProductIdsMap.get(categoryId);
            if (productIds == null) {
                productIds = new HashSet<>();
            }
            productIds.add(dataId);
            categoryProductIdsMap.put(categoryId, productIds);
            if (!ifOther && categoryId == 0) {
                ifOther = true;
            }
        }
        ProductCategoryChartDTO productCategoryChartDTO = setCategoryMap(corpid, selectCategoryId);
        Set<Long> parentIdSet = productCategoryChartDTO.getParentIdSet();
        if (ifOther && selectCategoryId == 0L) {
            //顶级分类需要包含“无分类”
            parentIdSet.add(0L);
        }
        //分类id--分类名
        Map<Long, String> categoryIdNameMap = productCategoryChartDTO.getCategoryIdNameMap();
        //分类id--该分类销量
        Map<Long, Double> categoryIdNumMap = productCategoryChartDTO.getCategoryIdNumMap();
        //分类id--其对应子类ids
        Map<Long, Set<Long>> childCategoryIdListMap = productCategoryChartDTO.getChildCategoryIdSetMap();
        //临时存放（只显示前面销量高的一部分分类）：分类id--该分类销量
        Map<Long, Double> tempCategoryIdNumMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        /*
        父分类处理--如果是统计父级分类的时候，需要把子分类的销量也加上
         */
        //顶级分类ids
        for (Long categoryId : parentIdSet) {
            /*
            （1）父分类自己本身包含的产品的销量
             */
            //属于该分类的产品id列表
            Set<Long> productIdList = categoryProductIdsMap.get(categoryId);
            setCategoryNum(categoryId, productIdList, productIdNumMap, categoryIdNumMap);
            /*
            （2）该父分类的子分类包含的产品的销量
             */
            //该父分类的子分类
            Set<Long> childCategoryIdList = childCategoryIdListMap.get(categoryId);
            if (childCategoryIdList != null) {
                for (Long childId : childCategoryIdList) {
                    //属于该子分类的产品id列表
                    Set<Long> childProductIdList = categoryProductIdsMap.get(childId);
                    //子分类产品销量也算入父分类内，所以分类id传入的是categoryId，而不是childId
                    //传入的是categoryId
                    setCategoryNum(categoryId, childProductIdList, productIdNumMap, categoryIdNumMap);
                }
            }
            tempCategoryIdNumMap.put(categoryId, categoryIdNumMap.get(categoryId));
        }
        //categoryId--排名值
        Integer size = 30;
        Map<Long, Integer> corpRankMap = SortHelp.sortMapValueDouble4KeyLong(tempCategoryIdNumMap, size);
        //最终显示到页面上的：分类id--分类名
        Map<Long, String> retCategoryIdNameMap = new LinkedHashMap<>();
        //最终显示到页面上的：分类id--该分类销量
        Map<Long, Double> retCategoryIdNumMap = new LinkedHashMap<>();
        Integer tempIndex = 1;
        for (Map.Entry<Long, Integer> entry : corpRankMap.entrySet()) {
            Long categoryId = entry.getKey();
            if (tempIndex > size) {
                //超过30的都置为“剩余其他分类”
                Double num = retCategoryIdNumMap.get(-1L);
                num = (num == null) ? Double.valueOf(0) : num;
                num += tempCategoryIdNumMap.get(categoryId);

                retCategoryIdNameMap.put(-1L, "剩余其他分类");
                retCategoryIdNumMap.put(-1L, num);
                continue;
            }
            tempIndex++;

            //页面饼图显示的数据
            retCategoryIdNameMap.put(categoryId, categoryIdNameMap.get(categoryId));
            retCategoryIdNumMap.put(categoryId, tempCategoryIdNumMap.get(categoryId));
        }
        List<ResultDataSeriesChartVO> list = new ArrayList<>();
        for (Map.Entry<Long, Double> entry : retCategoryIdNumMap.entrySet()) {
            Long categoryId = entry.getKey();
            ResultDataSeriesChartVO resultDataSeriesChartVO = new ResultDataSeriesChartVO();
            //分类名
            String realName = retCategoryIdNameMap.get(categoryId);
            //分类下产品数量
            String num = StringUtil.formatDouble(retCategoryIdNumMap.get(categoryId),2,1);
            resultDataSeriesChartVO.setValue(num);
            //"无分类[1,111,119,252.91]"
            String name = realName + "[" + num + "]";
            resultDataSeriesChartVO.setName(name);
            list.add(resultDataSeriesChartVO);
        }

        //分类销量无穿透
        ThroughDTO throughDto = null;
        StatisticResultPieInfoDTO statisticResultPieInfoDto = new StatisticResultPieInfoDTO("产品分类销量", list, throughDto);
        return systemChartHelp.packageResult4PieNoTable(statisticResultPieInfoDto, chartResultPojo, chartResultDetailVO);
    }

    /**
     * 查询合同，合同产品的公用方法
     *
     * @param chartResultPojo 入参
     * @param chartResultDetailVO 出参
     * @param isRed 是否统计红冲
     * @param seriesName 柱状图上面的柱内的名称
     * @return java.lang.Object
     * @throws XbbException findByScroll时抛出的异常
     * @author 徐俊杰
     * @date 2019/4/4 11:30
     * @since v1.0
     */
    Object contractProductTop(ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO, Integer isRed, String seriesName,Boolean isReturn) throws XbbException {
        String corpid = chartResultPojo.getCorpid();
        chartResultPojo.setValueTypeEnum(ValueTypeEnum.VALUE_DOUBLE);
        //查询合同产品入参
        BoolQueryBuilder contractProductQuery = chartQueryHelp.contractProductQuery(chartResultPojo, isRed);
        String termsField = ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.PRODUCT_ID);
        String sumField = ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.PRODUCT_NUM);
        //以产品id terms聚合，然后以数量sum：根据第二层的sum结果降序排列，获取前10个产品（分桶）
        TopResultPojo topResultPojo = systemChartHelp.getTopResultPojo4ProductTop(corpid, contractProductQuery, termsField, sumField,isReturn);
        return systemChartHelp.result4Top(chartResultPojo, chartResultDetailVO, topResultPojo, seriesName);
    }

    /**
     * 合同产品相关信息（合同id集合，产品id集合，产品id-总量map，产品id-总销售额map，产品id-总成本map）
     *
     * @param chartResultPojo 入参
     * @return com.xbongbong.pro.statistic.result.pojo.dto.ContractProductChartDTO
     * @throws XbbException 查询合同和合同产品时抛出的异常
     * @author 徐俊杰
     * @date 2019/4/23 10:01
     * @since v1.0
     */
    private ContractProductChartDTO contractProductDTO(ChartResultPojo chartResultPojo) throws XbbException {
        BoolQueryBuilder contractProductQuery = chartQueryHelp.contractProductQuery(chartResultPojo, null);
        List<String> fieldList = Arrays.asList(ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.PRODUCT_ID), ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.CONTRACT_ID), ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.PRODUCT_NUM), ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.PRICE), ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.COST));
        List<PaasFormDataEntity> contractProductList = esHelper.findByScroll(IndexTypeEnum.IDX_SAAS_CONTRACT_PRODUCT, contractProductQuery, PaasFormDataEntity.class, fieldList);
        List<ContractProductEntity> contractProductEntities = transferSubFormHelper.transferFormDataToContractProductList(contractProductList);
        //初始化
        Set<Long> productIdSet = new HashSet<>();
        Set<Long> contractIdSet = new HashSet<>();
        Map<Long, Double> productIdSumPriceMap = new HashMap<>(contractProductEntities.size());
        Map<Long, Double> productIdSumCostMap = new HashMap<>(contractProductEntities.size());
        for (ContractProductEntity entity : contractProductEntities) {
            Long productId = entity.getProductId();
            productIdSet.add(productId);
            Long contractId = entity.getContractId();
            contractIdSet.add(contractId);
            //当前产品信息
            Double num = entity.getProductNum();
            num = num == null ? Double.valueOf(0) : num;
            Double price = entity.getPrice();
            price = price == null ? Double.valueOf(0) : price;
            Double cost = entity.getCost();
            cost = cost == null ? Double.valueOf(0) : cost;
            //已经计算过的产品信息
            Double valuePrice = productIdSumPriceMap.get(productId);
            Double valueCost = productIdSumCostMap.get(productId);
            valuePrice = (valuePrice == null) ? price * num : price * num + valuePrice;
            valueCost = (valueCost == null) ? cost * num : cost * num + valueCost;
            //包装
            productIdSumPriceMap.put(productId, valuePrice);
            productIdSumCostMap.put(productId, valueCost);
        }
        //包装外部要使用的参数
        ContractProductChartDTO contractProductChartDTO = new ContractProductChartDTO();
        contractProductChartDTO.setContractProductList(contractProductEntities);
        contractProductChartDTO.setProductIdSet(productIdSet);
        contractProductChartDTO.setContractIdSet(contractIdSet);
        contractProductChartDTO.setProductIdSumPriceMap(productIdSumPriceMap);
        contractProductChartDTO.setProductIdSumCostMap(productIdSumCostMap);
        return contractProductChartDTO;
    }

    /**
     * 查询产品相关信息（没有模糊查询）
     *
     * @param corpid                  公司id
     * @param productIdSet 合同产品中的产品Id
     * @return com.xbongbong.pro.statistic.pojo.dto.ProductChartDTO
     * @throws XbbException 查询产品列表抛出的异常
     * @author 徐俊杰
     * @date 2019/3/10 10:42
     * @since v1.0
     */
    private ProductChartDTO productDTO(String corpid, Set<Long> productIdSet, Integer page, Integer pageSize) throws XbbException {
        //初始化DTO
        ProductChartDTO productChartDTO = new ProductChartDTO();
        //查询产品列表
        XbbAggregatedPage<PaasFormDataEntity> esEntities = formHelp.getProductListByPids(corpid, productIdSet, page, pageSize);
        List<PaasFormDataEntity> productList = new ArrayList<>();
        int rowCounts = 0;
        if (esEntities != null) {
            productList = esEntities.getContent();
            rowCounts = (int) esEntities.getTotalElements();
        }
        PageHelper pageHelper = PageHelperUtil.initPageHelper(rowCounts, page, pageSize, PageConstant.DEFAULT_PAGE_SIZE_SMALL);
        productChartDTO.setPageHelper(pageHelper);
        //产品列表中取出参数
        String parentIdAttr = ProductEnum.PARENT_ID.getAttr();
        Map<Long, JSONObject> productIdDataMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        Set<Long> parentIdSet = new HashSet<>();
        for (PaasFormDataEntity entity : productList) {
            Long productId = entity.getDataId();
            JSONObject data = entity.getData();
            productIdDataMap.put(productId, data);
            Long parentId = data.getLong(parentIdAttr);
            if (!Objects.equals(parentId, 0L)) {
                parentIdSet.add(parentId);
            }
        }
        //父级产品id--产品编号map
        Map<Long, String> parentIdNoMap = formHelp.getParentIdNoMap(corpid, parentIdSet);
        //包装DTO
        productChartDTO.setProductIdDataMap(productIdDataMap);
        productChartDTO.setParentIdNoMap(parentIdNoMap);
        return productChartDTO;
    }

    /**
     * 判断进销存是否开启
     *
     * @param corpid 公司Id
     * @return boolean
     * @author 徐俊杰
     * @date 2019/3/7 8:50
     * @since v1.0
     */
    private boolean ifJXCOpen(String corpid) {
        PaasAppEntity paasAppEntity = paasAppModel.getByAlias(XbbRefTypeEnum.JXC.getAlias(), corpid);
        if (paasAppEntity == null) {
            return false;
        }
        Integer enable = paasAppEntity.getEnable();
        return Objects.equals(BasicConstant.ONE, enable);
    }

    /**
     * 包装产品表参数
     *
     * @param product 产品
     * @param sumCost 总成本
     * @param parentIdNoMap 父产品id-编号map
     * @param unitMap 单位map
     * @param numFormatEnum
     * @return java.util.List<com.xbongbong.pro.statistic.result.pojo.vo.ResultDataTableVO>
     * @author 徐俊杰
     * @date 2019/3/31 12:59
     * @since v1.0
     */
    private List<ResultDataTableVO> formatObject(JSONObject product, double sumCost, double sumPrice, Map<Long, String> parentIdNoMap, Map<String, String> unitMap, NumFormatEnum numFormatEnum) {
        String productNo = formHelp.getProductNo(parentIdNoMap, product);
        String name = product.getString(ProductEnum.NAME.getAttr());
        //TODO 处理规格，调用的方法还没搬 by:徐俊杰
        String specification = specificationModel.joinSpecification(product.getString(ProductEnum.SPECIFICATION_LINK_TEXT.getAttr()));
        String unit = unitMap.getOrDefault(product.getString(ProductEnum.UNIT.getAttr()), "");
        //销售金额
        String sumPriceStr = ChartResultUtil.formatVal2Thousands(BigDecimal.valueOf(sumPrice), numFormatEnum);
        //产品成本金额sumCost要从外部传入，因为根据进销存是否开启结果不同
        String sumCostStr = ChartResultUtil.formatVal2Thousands(BigDecimal.valueOf(sumCost), numFormatEnum);
        //产品毛利
        double profit = sumPrice - sumCost;
        String profitStr = ChartResultUtil.formatVal2Thousands(BigDecimal.valueOf(profit), numFormatEnum);
        //产品毛利率---默认乘以100，前端直接在后面拼接“%”
        double profitRate = (sumPrice == 0) ? 0 : Arith.div(profit, sumPrice) * 100;
        StringBuilder profitRateStr = new StringBuilder(StringUtil.formatDouble(profitRate)).append("%");
        List<ResultDataTableVO> dataList = new ArrayList<>();
        dataList.add(new ResultDataTableVO(productNo, null));
        dataList.add(new ResultDataTableVO(name, null));
        dataList.add(new ResultDataTableVO(specification, null));
        dataList.add(new ResultDataTableVO(unit, null));
        dataList.add(new ResultDataTableVO(sumPriceStr, null));
        dataList.add(new ResultDataTableVO(sumCostStr, null));
        dataList.add(new ResultDataTableVO(profitStr, null));
        dataList.add(new ResultDataTableVO(String.valueOf(profitRateStr), null));
        return dataList;
    }

    /**
     * 获取产品成本
     *
     * @param corpid 公司id
     * @param productIdIn 产品id集合
     * @param contractIdSet 合同id集合
     * @return java.util.Map<java.lang.String,java.lang.Object>
     * @throws XbbException 出库相关异常
     * @author 徐俊杰
     * @date 2019/4/25 20:03
     * @since v1.0
     */
    private Map<String, Object> getProductCost(String corpid, Set<Long> productIdIn, Set<Long> contractIdSet, List<ContractProductEntity> contractProductList) throws XbbException {
        //产品id--该产品的总出库成本
        Map<Long, Double> costMap4Out = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        //产品id--该产品的未出库总成本
        Map<Long, Double> costMap4No = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        if (contractProductList.size() > 0) {
            //获取outStock相关数据
            OutstockChartDTO outStockChartDTO = getOutstockChartDTO(corpid, contractIdSet);
            Set<Long> outstockIdSet = outStockChartDTO.getOutstockIdSet();
            Map<Long, Long> oId2CidMap = outStockChartDTO.getOId2CidMap();
            //获取outStockProduct相关数据
            OutStockProductChartDTO outStockProductChartDTO = getOutstockProductDTO(corpid, productIdIn, outstockIdSet, oId2CidMap);
            //一、计算-总出库成本
            costMap4Out = outStockProductChartDTO.getCostMap4Out();
            //“cid_pid”--对应的出库产品list
            Map<String, List<OutstockProductEntity>> cidPid2OutProListMap = outStockProductChartDTO.getCidPid2OutProListMap();
            //处理过产品数量和成本的合同产品列表
            Collection<ContractProductEntity> mergeContractProduct = mergeContractProduct4Multi(contractProductList);
            contractProductList.clear();
            contractProductList.addAll(mergeContractProduct);
            //二、计算-未出库的产品成本
            costMap4No = getCostMap4No(contractProductList, cidPid2OutProListMap);
        }
        Map<String, Object> retMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        retMap.put("costMap4Out", costMap4Out);
        retMap.put("costMap4No", costMap4No);
        return retMap;
    }

    /**
     * 以合同Id列表获取出库单列表
     *
     * @param corpid        公司Id
     * @param contractIdSet 合同Id集合
     * @return java.util.List<com.xbongbong.paas.domain.entity.PaasFormDataEntityExt>
     * @throws XbbException findByScroll抛出异常
     * @author 徐俊杰
     * @date 2019/3/7 11:12
     * @since v1.0
     */
    private OutstockChartDTO getOutstockChartDTO(String corpid, Set<Long> contractIdSet) throws XbbException {
        OutstockChartDTO outstockChartDTO = new OutstockChartDTO();
        //筛选条件
        BoolQueryBuilder outstockQuery = chartQueryHelp.outstockQuery4ContractId(corpid, contractIdSet);
        String refIdData = OutstockEnum.getAttrConnectData(OutstockEnum.REF_ID);
        //需要获取的字段
        List<String> fieldList = Arrays.asList(FieldTypeEnum.DATAID.getAlias(), refIdData);
        List<PaasFormDataEntity> outstockList = esHelper.findByScroll(IndexTypeEnum.IDX_SAAS_OUTSTOCK, outstockQuery, PaasFormDataEntity.class, fieldList);
        //出库单ids
        Set<Long> outstockIdSet = new HashSet<>();
        outstockIdSet.add(-1L);
        //出库单id--合同id
        Map<Long, Long> oId2CidMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        String refIdAttr = OutstockEnum.REF_ID.getAttr();
        for (PaasFormDataEntity entity : outstockList) {
            Long outstockId = entity.getDataId();
            outstockIdSet.add(outstockId);
            JSONObject data = entity.getData();
            if (data == null) {
                continue;
            }
            Long refId = data.getLong(refIdAttr);
            if (refId == null) {
                continue;
            }
            oId2CidMap.put(outstockId, refId);
        }
        outstockChartDTO.setOutstockIdSet(outstockIdSet);
        outstockChartDTO.setOId2CidMap(oId2CidMap);
        return outstockChartDTO;
    }

    /**
     * 获取outStockProductChartDTO中的costMap4Out和cidPid2OutProListMap
     *
     * @param corpid       公司Id
     * @param productIdIn  产品IdSet
     * @param outstockIdIn 出库单IdSet
     * @param oId2CidMap   出库单id--合同id
     * @return com.xbongbong.pro.statistic.pojo.dto.OutStockProductChartDTO
     * @author 徐俊杰
     * @date 2019/3/11 11:11
     * @since v1.0
     */
    private OutStockProductChartDTO getOutstockProductDTO(String corpid, Set<Long> productIdIn, Set<Long> outstockIdIn, Map<Long, Long> oId2CidMap) {
        OutStockProductChartDTO outStockProductChartDTO = new OutStockProductChartDTO();
        //得到所有出库产品：限制出库单id、限制产品id（因为只需统计合同销售的产品，超发出库的其他产品不算成本毛利--因超发的，无对应的产品销售价
        Map<String, Object> map = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        map.put("corpid", corpid);
        map.put("del", DelEnum.NORMAL.getDel());
        //限制productIdIn：排除超发出库的
        map.put("productIdIn", productIdIn);
        map.put("outstockIdIn", outstockIdIn);
        List<OutstockProductEntity> outstockProductList = outstockProductModel.findEntitys(map);
        /*
        计算-总出库成本
        （1）得到，一个产品，在一个合同的所有出库。为后续算出未出库的产品成本做准备
        （2）该map含义：“cid_pid”--对应的出库产品list。
        为什么用“cid_pid”作为key，而value是一个list？答案：一个产品可以多个合同销售，一个合同有多个出库单，一个出库单有多个产品，
        即cid和pid组成的key不是唯一关系，而是对应多个出库产品。所以，用pid结合cid，才能把一个合同下面的所有出库产品列表得到
         */
        Map<String, List<OutstockProductEntity>> cidPid2OutProListMap = new HashMap<>(outstockProductList.size());
        //产品id--该产品的总出库成本
        Map<Long, Double> costMap4Out = new HashMap<>(outstockProductList.size());
        for (OutstockProductEntity entity : outstockProductList) {
            //产品id
            Long productId = entity.getProductId();
            //处理一个产品的总出库成本
            double cost = 0D;
            Double singleCost = entity.getCost();
            Double productNum = entity.getProductNum();
            //增加了判空
            if (singleCost != null && productNum != null) {
                cost = Arith.mul(singleCost, productNum);
            }
            Double totalCost = costMap4Out.get(productId);
            totalCost = (totalCost == null) ? cost : totalCost + cost;
            costMap4Out.put(productId, totalCost);
            //处理cidPid2OutProListMap:一个合同的所有出库
            //合同id---出库产品的合同id要通过出库单来获取
            Long contractId = oId2CidMap.get(entity.getOutWarehouseId());
            String key = contractId + "_" + productId;
            List<OutstockProductEntity> list = cidPid2OutProListMap.get(key);
            if (list == null) {
                list = new ArrayList<>();
            }
            list.add(entity);
            cidPid2OutProListMap.put(key, list);
        }
        outStockProductChartDTO.setCostMap4Out(costMap4Out);
        outStockProductChartDTO.setCidPid2OutProListMap(cidPid2OutProListMap);
        return outStockProductChartDTO;
    }

    /**
     * 计算-未出库的产品成本
     *
     * @param contractProductList  处理过产品数量和成本的合同产品列表
     * @param cidPid2OutProListMap “cid_pid”--对应的出库产品list
     * @return java.util.Map<java.lang.Long,java.lang.Double>
     * @author 徐俊杰
     * @date 2019/3/11 13:34
     * @since v1.0
     */
    private Map<Long, Double> getCostMap4No(List<ContractProductEntity> contractProductList, Map<String, List<OutstockProductEntity>> cidPid2OutProListMap) {
        Map<Long, Double> costMap4No = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        /*二、计算-未出库的产品成本
         *同一个合同中的产品可以重复添加，所以需要将同一个合同中id相同的产品数量叠加，成本和销售价计算平均值 */
        //经过it.remove()后，剩余的contractProductList都为未出库的
        Iterator<ContractProductEntity> it = contractProductList.iterator();
        while (it.hasNext()) {
            ContractProductEntity contractProductEntity = it.next();
            //产品id
            Long productId = contractProductEntity.getProductId();
            Long contractId = contractProductEntity.getContractId();
            String key = contractId + "_" + productId;
            List<OutstockProductEntity> outstockProductEntityList = cidPid2OutProListMap.get(key);
            if (outstockProductEntityList == null) {
                //说明该合同内的该产品未出库
                continue;
            }
            for (OutstockProductEntity outstockProductEntity : outstockProductEntityList) {
                //合同销售产品数-出库产品数
                double num = Arith.sub(contractProductEntity.getProductNum(), outstockProductEntity.getProductNum());
                if (num <= 0.00000001) {
                    //如果合同产品数量少于已出库数量，那么就将该产品去掉不显示.（出现这种情况是因为合同重新编辑数量变少了）
                    it.remove();
                    break;
                } else {
                    //已更新为剩余数量
                    contractProductEntity.setProductNum(num);
                }
            }
        }
        for (ContractProductEntity entity : contractProductList) {
            //产品id
            Long productId = entity.getProductId();
            //处理一个产品的总未出库成本---此时的productNum经过上面的Iterator处理，已经减去了出库数量
            double cost = Arith.mul(entity.getProductNum(), entity.getCost());
            Double totalCost = costMap4No.get(productId);
            totalCost = (totalCost == null) ? cost : totalCost + cost;
            costMap4No.put(productId, totalCost);
        }
        return costMap4No;
    }

    /**
     * 同一个合同中的产品可以重复添加，所以需要将同一个合同中id相同的产品数量叠加，成本计算平均值
     * 注：此方法是针对的多个合同的contractProductEntityList，而不只是同一个合同的contractProductEntityList
     *
     * @param contractProductEntityList 多个合同的产品list
     * @return java.util.Collection<com.xbongbong.saas.domain.entity.ContractProductEntity> 返回key是“contractId_productId”的map的values()
     * @author 徐俊杰
     * @date 2019/3/7 13:49
     * @since v1.0
     */
    private Collection<ContractProductEntity> mergeContractProduct4Multi(List<ContractProductEntity> contractProductEntityList) {
        Map<String, ContractProductEntity> contractProductMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        for (ContractProductEntity contractProduct : contractProductEntityList) {
            Long contractId = contractProduct.getContractId();
            Long productId = contractProduct.getProductId();
            String key = contractId + "_" + productId;
            if (contractProductMap.containsKey(key)) {
                ContractProductEntity mergeContractProduct = contractProductMap.get(key);
                double mProductNum = mergeContractProduct.getProductNum();
                double mCost = mergeContractProduct.getCost();
                double cProductNum = contractProduct.getProductNum();
                double cCost = contractProduct.getCost();

                //合并产品数量
                double mergeNum = Arith.add(mProductNum, cProductNum);
                mergeContractProduct.setProductNum(mergeNum);
                //合并cost，并取平均值= 总成本 ÷ 总数量
                double mergeTotalCost = Arith.add(Arith.mul(mProductNum, mCost), Arith.mul(cProductNum, cCost));
                if (Double.compare(mergeNum, 0) > 0) {
                    mergeContractProduct.setCost(Arith.div(mergeTotalCost, mergeNum));
                } else {
                    mergeContractProduct.setCost(0D);
                }
                contractProductMap.put(key, mergeContractProduct);
            } else {
                contractProductMap.put(key, contractProduct);
            }
        }
        return contractProductMap.values();
    }

    /**
     * 初始化要统计的产品分类（顶级或子级）的信息
     *
     * @param corpid           公司id
     * @param selectCategoryId 父分类id，如果为0则统计所有父分类的销售占比情况
     * @return com.xbongbong.pro.statistic.result.pojo.dto.ProductCategoryChartDTO
     * @author 徐俊杰
     * @date 2019/4/8 14:49
     * @since v1.0
     */
    private ProductCategoryChartDTO setCategoryMap(String corpid, Long selectCategoryId) {
        ProductCategoryChartDTO productCategoryDTO = new ProductCategoryChartDTO();
        Map<String, Object> param = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        CommentUtil.addToMap(param, "corpid", corpid);
        CommentUtil.addToMap(param, "del", DelEnum.NORMAL.getDel());
        List<ProductCategoryEntity> allCategoryList = productCategoryModel.findEntitys(param);
        //顶级分类的ids
        Set<Long> parentIdSet = new HashSet<>();
        //分类id--分类名
        Map<Long, String> categoryIdNameMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        //分类id--该分类销量
        Map<Long, Double> categoryIdNumMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        if (selectCategoryId == 0L) {
            categoryIdNameMap.put(0L, "无分类");
            categoryIdNumMap.put(0L, 0d);
        }
        //分类id--其对应子类ids
        Map<Long, Set<Long>> childCategoryIdListMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        //父级和它的下一级的对应关系
        Map<Long, List<ProductCategoryEntity>> supSubListDepMap = productCategoryModel.getSupSubListMap(allCategoryList);
        Set<Long> alreadySet = new HashSet<>();
        for (ProductCategoryEntity category : allCategoryList) {
            Long categoryId = category.getId();
            //若节点已访问，则返回
            if (alreadySet.contains(categoryId)) {
                continue;
            }
            if (category.getParentId().equals(selectCategoryId)) {
                parentIdSet.add(categoryId);
                categoryIdNameMap.put(categoryId, category.getName());
                categoryIdNumMap.put(categoryId, 0d);

                String router = category.getRouter();
                //把该categoryId分类下面所有的子分类都归入categoryId内
                //用于递归的队列
                Queue<ProductCategoryEntity> accessDep = new LinkedList<ProductCategoryEntity>();
                accessDep.add(category);
                while (accessDep.size() > 0) {
                    ProductCategoryEntity tempCategory = accessDep.poll();
                    Long tempId = tempCategory.getId();
                    //若节点已访问，则返回
                    if (alreadySet.contains(tempId)) {
                        continue;
                    }
                    alreadySet.add(tempId);
                    String tempRouter = tempCategory.getRouter();
                    if (tempRouter.contains(router) && !tempRouter.equals(router)) {
                        Set<Long> childIdList = childCategoryIdListMap.get(categoryId);
                        if (childIdList == null) {
                            childIdList = new HashSet<>();
                        }
                        //子分类id
                        childIdList.add(tempId);
                        //父分类--其对应的子分类ids
                        childCategoryIdListMap.put(categoryId, childIdList);
                    }
                    List<ProductCategoryEntity> subList = supSubListDepMap.get(tempId);
                    if (subList != null && subList.size() > 0) {
                        accessDep.addAll(subList);
                    }
                }
            }
        }
        productCategoryDTO.setParentIdSet(parentIdSet);
        productCategoryDTO.setCategoryIdNameMap(categoryIdNameMap);
        productCategoryDTO.setCategoryIdNumMap(categoryIdNumMap);
        productCategoryDTO.setChildCategoryIdSetMap(childCategoryIdListMap);
        return productCategoryDTO;
    }

    /**
     * 设置某一个分类的产品销量
     *
     * @param categoryId       分类id
     * @param productIdList    某个分类的产品id列表
     * @param productIdNumMap  产品id-产品数量map
     * @param categoryIdNumMap 分类id-产品数量map
     * @author 徐俊杰
     * @date 2019/4/8 15:25
     * @since v1.0
     */
    private void setCategoryNum(Long categoryId, Set<Long> productIdList, Map<Long, Double> productIdNumMap, Map<Long, Double> categoryIdNumMap) {
        if (productIdList == null) {
            return;
        }
        double sumNum = 0;
        for (Long productId : productIdList) {
            Double num = productIdNumMap.get(productId);
            num = (num == null) ? Double.valueOf(0) : num;
            sumNum = Arith.add(sumNum, num);
        }
        //如果是统计父级分类的时候，需要把子分类的销量也加上 --- 已经存在的销量，加上所有子分类内的产品销量
        Double totalNum = categoryIdNumMap.get(categoryId);
        totalNum = (totalNum == null) ? Double.valueOf(0) : totalNum;
        totalNum = Arith.add(totalNum, sumNum);
        categoryIdNumMap.put(categoryId, totalNum);
    }

    /**
     * 得到销售的产品ids，并把所有的销量、销售额汇总
     * @param contractProductlist 合同销售产品list
     * @param idNumMap 产品id--产品数量
     * @param idNumPriceMap 产品id--产品数量*售价
     * @return java.util.Set<java.lang.Long>
     * @author zcp
     * @date 2019/4/10 16:34
     * @since v1.0
     * @version v1.0
     */
    private Set<Long> formatSales(List<ContractProductEntity> contractProductlist, Map<Long, Double> idNumMap, Map<Long, Double> idNumPriceMap) {
        Set<Long> productIdIn = new HashSet<>();
        for (ContractProductEntity entity : contractProductlist) {
            Long productId = entity.getProductId();
            productIdIn.add(productId);
            Double num = entity.getProductNum() == null ? Double.valueOf(0) : entity.getProductNum();
            Double price = entity.getPrice() == null ? Double.valueOf(0) : entity.getPrice();
            Double valueNum = idNumMap.get(productId);
            Double valuePrice = idNumPriceMap.get(productId);

            valueNum = (valueNum == null) ? num : valueNum + num;
            valuePrice = (valuePrice == null) ? price * num : price * num + valuePrice;
            idNumMap.put(productId, valueNum);
            idNumPriceMap.put(productId, valuePrice);
        }
        return productIdIn;
    }

    /**
     * 封装产品销售汇总表信息
     * @param corpid 公司id
     * @param idNumMap 产品id--产品数量
     * @param idNumPriceMap 产品id--产品数量*售价
     * @param categoryIdNameMap 分类id--分类名称
     * @param productInfoVoList 产品list
     * @param chartResultPojo
     * @return java.util.List<java.util.List<com.xbongbong.pro.statistic.result.pojo.vo.ResultDataTableVO>>
     * @throws XbbException
     * @author zcp
     * @date 2019/4/10 16:49
     * @since v1.0
     * @version v1.0
     */
    private List<List<ResultDataTableVO>> formatProductSaleTableData(String corpid, Map<Long, Double> idNumMap, Map<Long, Double> idNumPriceMap, Map<Long, String> categoryIdNameMap, List<ProductInfoVO> productInfoVoList, ChartResultPojo chartResultPojo) throws XbbException {
        Set<Long> parentIdIn = new HashSet<>();
        Set<Long> categoryIdIn = new HashSet<>();
        String categoryIdAttr = ProductEnum.CATEGORY_ID.getAttr();
        String parentIdAttr = ProductEnum.PARENT_ID.getAttr();
        Integer numFormat = chartResultPojo.getNumFormat();
        NumFormatEnum numFormatEnum = NumFormatEnum.getByCode(numFormat);
        for (ProductInfoVO product : productInfoVoList) {
            JSONObject data = JsonHelperUtil.getJSONObject(product.getData());
            categoryIdIn.add(data.getLong(categoryIdAttr));
            parentIdIn.add(data.getLong(parentIdAttr));
        }
        //父级产品id--产品编号map
        Map<Long, String> parentIdNoMap = formHelp.getParentIdNoMap(corpid, parentIdIn);
        //当未筛选分类时，没有去查分类categoryIdNameMap的size为0，所以要在此处查询一次需要的分类信息
        if (categoryIdNameMap.size() == 0) {
            List<ProductCategoryEntity> categoryList = productCategoryModel.getProductCategoryListByIdIn(corpid, categoryIdIn);
            for (ProductCategoryEntity entity : categoryList) {
                categoryIdNameMap.put(entity.getId(), entity.getName());
            }
        }

        String nameAttr = ProductEnum.NAME.getAttr();
        String specificationAttr = ProductEnum.SPECIFICATION_LINK_TEXT.getAttr();
        String unitAttr = ProductEnum.UNIT.getAttr();
        //获取启用的产品单位
        Map<String, String> unitMap = formHelp.getProductUnitItemMap(corpid);
        List<List<ResultDataTableVO>> tableData = new ArrayList<>();
        for (ProductInfoVO entityExt : productInfoVoList) {
            JSONObject data = JsonHelperUtil.getJSONObject(entityExt.getData());
            Long productId = entityExt.getId();
            String categoryName = categoryIdNameMap.get(data.getLongValue(categoryIdAttr));
            categoryName = StringUtil.isEmpty(categoryName) ? "" : categoryName;
            String unit = unitMap.getOrDefault(data.getString(unitAttr), "");
            double sumProductNum = idNumMap.getOrDefault(productId, 0D);
            double sumPrice = idNumPriceMap.getOrDefault(productId, 0D);
            List<ResultDataTableVO> voList = new ArrayList<>();
            voList.add(new ResultDataTableVO(categoryName, null));
            voList.add(new ResultDataTableVO(formHelp.getProductNo(parentIdNoMap, data), null, productId));
            voList.add(new ResultDataTableVO(data.getString(nameAttr), null, productId));
            String specification = specificationModel.joinSpecification(data.getString(specificationAttr));
            voList.add(new ResultDataTableVO(specification, null));
            voList.add(new ResultDataTableVO(unit, null));
            voList.add(new ResultDataTableVO(StringUtil.formatDouble(sumProductNum), null));
            voList.add(new ResultDataTableVO(ChartResultUtil.formatVal2Thousands(BigDecimal.valueOf(sumPrice), numFormatEnum), null));
            tableData.add(voList);
        }
        return tableData;
    }
    /**
     * 同时获取并封装环比、同比统计结果
     * 
     * @param chartResultPojo 入参
     * @param legendList 图的legend
     * @param unitList 单位
     * @return com.xbongbong.pro.statistic.result.pojo.dto.StatisticResultDTO
     * @throws XbbException 合同筛选条件异常
     * @author 徐俊杰
     * @date 2019/4/26 9:24
     * @since v1.0
     */
    private StatisticResultDTO getStatisticResultDto4MomYoy(ChartResultPojo chartResultPojo, List<String> legendList, List<String> unitList) throws XbbException {
        ElasticAggsPojo thisMonthAggsPojo = productSalesAgg(chartResultPojo);
        Integer startTime = chartResultPojo.getStartTime();
        Integer endTime = chartResultPojo.getEndTime();
        //------环比结果
        //先把chartResultPojo拷贝出一份，下面都用chartResultPojoCopy，防止时间的变更（环比、同比需要改变开始结束时间）对原有chartResultPojo内参数的影响
        ChartResultPojo chartResultPojoCopy = new ChartResultPojo();
        BeanUtil.copyProperties(chartResultPojo, chartResultPojoCopy);
        ChartResultUtil.momOrYoyTime(chartResultPojoCopy, true, startTime, endTime);
        ElasticAggsPojo lastMonthAggsPojo = productSalesAgg(chartResultPojoCopy);
        ChartResultUtil.momOrYoyTime(chartResultPojoCopy, false, startTime, endTime);
        ElasticAggsPojo lastYearAggsPojo = productSalesAgg(chartResultPojoCopy);
        //------处理环比、同比比例
        ElasticAggsPojo resultMom = systemChartHelp.getMomYoyResult(thisMonthAggsPojo, lastMonthAggsPojo);
        ElasticAggsPojo resultYoy = systemChartHelp.getMomYoyResult(thisMonthAggsPojo, lastYearAggsPojo);

        List<StatisticResultInfoDTO> statisticResultInfoDtoList = systemChartHelp.getInfoDtos4MomYoy(chartResultPojo, legendList, unitList, thisMonthAggsPojo, lastMonthAggsPojo, lastYearAggsPojo, resultMom, resultYoy);
        return new StatisticResultDTO(true, thisMonthAggsPojo.getTotal(), null, statisticResultInfoDtoList);
    }
    /**
     * 产品销量聚合结果
     *
     * @param chartResultPojo 入参
     * @return com.xbongbong.pro.statistic.result.pojo.ElasticAggsPojo
     * @author 徐俊杰
     * @date 2019/4/26 9:23
     * @since v1.0
     */
    private ElasticAggsPojo productSalesAgg(ChartResultPojo chartResultPojo) throws XbbException {
        BoolQueryBuilder productSalesQuery = chartQueryHelp.productSalesSingleQuery4Parent(true, null, chartResultPojo);
        //用子产品聚合时要塞入的条件
        BoolQueryBuilder aggsProductSalesQuery = chartQueryHelp.productSalesSingleQuery(chartResultPojo);
        String fieldTime = ContractEnum.getEsAttr4Keyword(ContractEnum.SIGN_TIME);
        String termsField = ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.PRODUCT_ID);
        String sumField = ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.PRODUCT_NUM);
        String childType = IndexTypeEnum.IDX_SAAS_CONTRACT_PRODUCT.getType();
        chartResultPojo.setValueTypeEnum(ValueTypeEnum.VALUE_DOUBLE);
        return systemChartHelp.getAgg4ProductSales(chartResultPojo, fieldTime, termsField, sumField, childType, IndexTypeEnum.IDX_SAAS_CONTRACT, productSalesQuery, aggsProductSalesQuery);
    }

    /**
     * 产品分类销量聚合结果(合同产品用父查子)
     *
     * @param chartResultPojo 入参
     * @return com.xbongbong.pro.statistic.result.pojo.ElasticAggsPojo
     * @throws XbbException 包装合同产品参数异常
     * @author 徐俊杰
     * @date 2019/5/11 16:31
     * @since v1.0
     */
    private ElasticAggsPojo productCategoryRateAgg(ChartResultPojo chartResultPojo) throws XbbException {
        //查询合同产品入参
        BoolQueryBuilder contractProductQuery = chartQueryHelp.contractProductQuery(chartResultPojo, null);
        //合同产品相关其他参数
        String termsField = ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.PRODUCT_ID);
        String sumField = ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.PRODUCT_NUM);
        String aggs = "aggs01";
        String aggsSub = "aggs02";
        TermsAggregationBuilder termsBuilder = esHelper.getTermsAggregation4Two(termsField, sumField, aggs, aggsSub);
        IndexTypeEnum indexTypeEnum = IndexTypeEnum.IDX_SAAS_CONTRACT_PRODUCT;
        //查询合同产品
        return esHelper.elasticAggs4Double(indexTypeEnum, contractProductQuery, termsBuilder);
    }
    /**
     * 合同产品相关参数(产品分类销量用)
     *
     * @param contractProductAgg 合同产品数据
     * @return com.xbongbong.pro.statistic.result.pojo.dto.ContractProductChartDTO
     * @author 徐俊杰
     * @date 2019/5/11 16:53
     * @since v1.0
     */
    private ContractProductChartDTO productCategoryRateDTO(ElasticAggsPojo contractProductAgg) {
        ContractProductChartDTO contractProductChartDTO = new ContractProductChartDTO();
        List<AggsBucketsPojo> aggsBucketsPojoList = contractProductAgg.getBuckets();
        //封装产品id集合和产品id-产品数量map
        Set<Long> productIdSet = new HashSet<>();
        Map<Long, Double> productIdNumMap = new HashMap<>(aggsBucketsPojoList.size());
        for (AggsBucketsPojo bucket : aggsBucketsPojoList) {
            //termField对应的值，productId
            String productIdStr = bucket.getKey();
            if (productIdStr == null) {
                continue;
            }
            Long productId = StringUtil.StringToLong(productIdStr);
            //sumField对应的值，productNum
            String productNumStr = bucket.getValue();
            if (productNumStr == null) {
                continue;
            }
            double productNum = StringUtil.toDouble(productNumStr);
            productIdSet.add(productId);
            productIdNumMap.put(productId, productNum);
        }
        contractProductChartDTO.setProductIdSet(productIdSet);
        contractProductChartDTO.setProductIdNumMap(productIdNumMap);
        return contractProductChartDTO;
    }
}
